<?php

Raise::load('core.helper.RaiseHelper');
Raise::load('core.collection.RaiseCollection');
Raise::load('core.io.file.RaiseFileSystem');
Raise::load('core.helper.config.RaiseSettings');
Raise::load('core.io.file.RaisePath');
Raise::load('core.plugin.IRaisePluginService');
Raise::load('core.plugin.RaisePlugin');

/**
 * RaisePluginManager class
 * manages plugins as well as provide services and hooking to plugins
 *
 * @author Sam-Mauris Yong / hellclanner at live dot com
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @package phpraise.core.plugin
 * @since 1.2
 */
class RaisePluginManager extends RaiseHelper {

    /**
     * A collection of IRaisePluginService which plugins have access to
     * @var RaiseCollection
     */
    private $services;

    /**
     * A collection of IRaisePluginHook
     * @var RaiseCollection
     */
    private $hooks;

    /**
     * A collection of active plugins
     * @var RaiseCollection
     */
    private $plugins;

    /**
     * A collection of names of disabled plugins
     * @var RaiseCollection
     */
    private $disabledPlugins;

    /**
     * Path to the plugins folder
     * @var string
     */
    private $path;

    /**
     * Create a new RaisePluginManager and load plugins from $path
     * @param string $path The path to the plugins folder
     */
    public function __construct($path = null){
        $this->hooks = new RaiseCollection();
        $this->services = new RaiseCollection();
        $this->plugins = new RaiseCollection();
        $this->disabledPlugins = new RaiseCollection();

        $this->path = $path;
        $this->loadPlugins();
    }

    /**
     * Get the path to the plugins folder that is being utilized
     * @return string
     */
    public function path(){
        return $this->path;
    }

    /**
     * Load all the plugins
     */
    private function loadPlugins(){
        if($this->path){
            $load = RaiseFileSystem::pathSearch(RaisePath::combine($this->path(), '/*/meta.ini'));
            foreach($load as $l){
                $settings = new RaiseSettings($l);

                $name = $settings->get('plugin', 'name');
                if(!$name){
                    throw new RaiseIncompatiblePluginException('Plugin name could not be retrieved. Path to plugin directory: \'' . $l . '\'');
                }
                if(strtolower($settings->get('targetFramework', 'name')) != 'phpraise'){
                    throw new RaiseIncompatiblePluginException('Plugin \'' . $name . '\' is not made for phpRaise: target framework name must be \'phpRaise\'.');
                }

                /**
                 * Only load if the plugin is enabled
                 */
                if($settings->get('plugin', 'enabled') == 1){
                    include(RaisePath::combine($l, '../' . $name . '.php'));

                    if(!class_exists($name)){
                        throw new RaiseIncompatiblePluginException('Entry class of Plugin \'' . $name . '\' could not be found even after being loaded. Could be a problem with the plugin.');
                    }

                    $p = new $name();
                    if(!($p instanceof RaisePlugin)){
                        throw new RaiseIncompatiblePluginException('Entry class of Plugin \'' . $name . '\' is not an implementation of RaisePlugin.');
                    }
                    $p->load($this, $settings);

                    $fwVersion = $p->targetFramework();
                    if($fwVersion->indexOf(Raise::version()->__toString()) == null){
                        throw new RaiseIncompatiblePluginException('Plugin \'' . $name . '\' is not compatible with the current version of phpRaise framework installed (Version ' . Raise::version() . ')');
                    }

                    $this->plugins->add($name, $p);
                }else{
                    $this->disabledPlugins->add($name);
                }
            }
        }
    }

    /**
     * Add a new action hook
     * @param string $name
     * @param IRaisePluginHook $hook
     */
    public function addHook($name, $hook){
        if(!$this->hooks->keyExists($name)){
            $this->hooks->add($name, new RaiseCollection());
        }
        $hookEntries = $this->hooks->get($name);
        $hookEntries->add($hook);
    }

    /**
     * Remove an action hook
     * @param string $name
     * @param IRaisePluginHook $hook (optional) If specified, only the specific hook will be removed.
     */
    public function removeHook($name, $hook = null){
        $hookEntries = $this->hooks->get($name);
        if($hookEntries instanceof RaiseCollection){
            if($hook && $hookEntries->count() > 0){
                $index = $hookEntries->indexOf($hook);
                if($index !== false){
                    $hookEntries->remove($index);
                }
            }elseif(!$hook){
                $this->hooks->remove($name);
            }
        }
    }

    /**
     * Call an action hook with the context
     * @param string $name The name of the hook to engage
     * @param mixed $context (optional) The data to pass to the call
     */
    public function callHook($name, &$context = null){
        $hookEntries = $this->hooks->get($name);
        if($hookEntries instanceof RaiseCollection && $hookEntries->count() > 0){
            foreach($hookEntries as $h){
                $h->call($context);
            }
        }
    }

    /**
     * Register a service for plugin's use
     * @param string $name The name of the service
     * @param IRaisePluginService $service The service to store
     */
    public function registerService($name, $service){
        $this->services->add($name, $service);
    }

    /**
     * Fetch a service for plugin's use from the service pool
     * @param string $name The name of the service to fetch
     * @return IRaisePluginService
     */
    public function service($name){
        return $this->services->get($name);
    }

    /**
     * Get the list of names of plugins that has been disabled.
     * @return string
     */
    public function disabledPlugins(){
        return $this->disabledPlugins;
    }

    /**
     * Check and return a collection of plugins that has updates
     * @return RaiseCollection
     */
    public function checkUpdates(){
        $updates = new RaiseCollection();
        foreach($this->plugins as $p){
            if($p->hasUpdate()){
                $updates->add($p);
            }
        }
        return $updates;
    }

    /**
     * Do updates for all plugins by downloading the latest version
     */
    public function updates(){
        foreach($this->plugins as $p){
            $p->update();
        }
    }

    /**
     * Uninstall a plugin from the system.
     * Note that this action is irreverisble
     * @param string $name Name of the plugin to remove
     */
    public function uninstall($name){
        $plugin = $this->plugins->get($name);
        if($plugin instanceof RaisePlugin){
            $plugin->uninstall();
            unset($plugin);
            $this->plugins->remove($name);
        }
    }

}
